package com.woniu.repaircenter.service.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.woniu.exception.XinanException;
import com.woniu.exception.repaircenter.NoticeException;
import com.woniu.exception.repaircenter.RepairException;
import com.woniu.repaircenter.models.dto.RepairNoticeDTO;
import com.woniu.repaircenter.models.param.AddRepairNoticeParam;
import com.woniu.repaircenter.models.param.SelectRepairNoticeParam;
import com.woniu.repaircenter.models.param.UpdateRepairNoticeParam;
import com.woniu.repaircenter.service.config.RabbitMQConfig;
import com.woniu.repaircenter.service.mapper.RepairNoticeMapper;
import com.woniu.repaircenter.service.model.RepairNotice;
import com.woniu.repaircenter.service.service.RepairNoticeService;
import com.woniu.server.RedisKey;
import com.woniu.server.RedisUtil;
import com.woniu.utils.EnumException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 维修公告表 服务实现类
 */
@Service
public class RepairNoticeServiceImpl extends ServiceImpl<RepairNoticeMapper, RepairNotice> implements RepairNoticeService {

    @Autowired
    private RepairNoticeMapper repairNoticeMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 添加公告
     * @param arnp 维修公告的表单参数，包含公告的相关信息
     * @return 返回插入操作的影响行数，如果为0，则表示插入失败
     * @throws Exception 如果插入失败，则抛出异常
     */
    @Override
    public Integer insertRepairNotice(AddRepairNoticeParam arnp) throws Exception {
        // 验证添加公告的表单信息是否合法
        validateAddRepairNoticeForm(arnp);
        // 根据表单信息和用户名创建一个维修公告对象
        RepairNotice repairNotice = createRepairNoticeFromForm(arnp);

        // 调用mapper层的方法，将维修公告信息插入数据库
        Integer result = repairNoticeMapper.insertRepairNotice(repairNotice);
        // 如果插入失败，抛出异常
        if (result == 0) {
            throw new NoticeException(EnumException.UPLOAD_FAILED);
        }
        // 返回插入操作的影响行数
        return result;
    }

    /**
     * 查询公告列表
     */
    @Override
    public PageInfo<RepairNoticeDTO> listRepairNotice(SelectRepairNoticeParam srnp) throws Exception {
        // 校验查询参数的合法性
        validateSelectRepairNoticeForm(srnp);
        // 初始化分页参数，默认页码为1，默认每页大小为10
        int pageNum = srnp.getPageNum() != null ? srnp.getPageNum() : 1;
        int pageSize = srnp.getPageSize() != null ? srnp.getPageSize() : 10;
        // 启动分页查询
        PageHelper.startPage(pageNum, pageSize);
        // 将查询参数转换为RepairNotice对象
        RepairNotice repairNotice = BeanUtil.copyProperties(srnp, RepairNotice.class);
        // 调用Mapper方法查询公告列表
        List<RepairNoticeDTO> repairNoticeDTOS = repairNoticeMapper.listRepairNotice(repairNotice);
        // 初始化PageInfo对象用于返回查询结果
        PageInfo<RepairNoticeDTO> pageInfo = new PageInfo<>(repairNoticeDTOS);
        // 设置公告列表的描述信息
        setDescriptions(repairNoticeDTOS);
        // 返回查询结果
        return pageInfo;
    }

    /**
     * 修改公告
     * @param urnp 包含要更新的公告信息的参数对象
     * @param userName 操作者的用户名，用于审计或日志记录
     * @return 返回更新操作影响的行数，0表示更新失败
     * @throws XinanException 如果更新操作失败，抛出此异常
     */
    @Override
    public Integer updateRepairNotice(UpdateRepairNoticeParam urnp, String userName) throws XinanException {
        // 验证更新公告的表单是否合法
        validateUpdateRepairNoticeForm(urnp);
        // 根据公告ID获取公告对象
        RepairNotice repairNotice = getRepairNoticeById(urnp.getNoticeId());
        // 更新公告的发布日期为当前时间
        repairNotice.setReleaseDate(LocalDateTime.now().toString());
        // 设置公告的状态
        if(urnp.getState() != null){
            repairNotice.setState(urnp.getState());
            repairNotice.setPublisher(userName);
        }
        if (urnp.getDelFlag()!= null){
            repairNotice.setDelFlag(urnp.getDelFlag());
        }
        // 调用Mapper层的方法更新数据库中的公告信息
        Integer result = repairNoticeMapper.updateRepairNotice(repairNotice);
        // 如果更新失败，抛出自定义的NoticeException异常
        if (result == 0) {
            throw new NoticeException(EnumException.REPAIR_NOTICE_UPDATE_FAILED);
        }
        // 返回更新操作影响的行数
        return result;
    }

    /**
     * 根据公告ID查询公告详情
     * @param noticeId 公告ID，用于查询公告详情
     * @return 返回包含公告详情和总浏览次数的DTO对象
     * @throws Exception 如果查询过程中发生错误，则抛出异常
     */
    @Override
    public RepairNoticeDTO selectRepairNotice(Long noticeId) throws Exception {
        // 构造Redis键，用于存储和查询公告的浏览次数
        String redisKey = RedisKey.noticeView(noticeId.toString());
        // 增加Redis中该公告的浏览次数
        redisUtil.increment(redisKey);
        // 从Redis中获取当前公告的浏览次数
        Long redisViewCount = redisUtil.getLong(redisKey);
        // 验证公告ID的有效性
        validateNoticeId(noticeId);
        // 从数据库中获取公告信息
        RepairNotice repairNotice = getRepairNoticeById(noticeId);
        // 将数据库中的公告信息转换为DTO对象
        RepairNoticeDTO repairNoticeDTO = BeanUtil.toBean(repairNotice, RepairNoticeDTO.class);
        // 获取数据库中存储的公告浏览次数
        Integer dbViewCount = repairNotice.getBrowseTimes();
        // 计算总浏览次数（数据库中的浏览次数加上Redis中的浏览次数）
        Long totalViewCount = dbViewCount + redisViewCount;
        // 设置公告DTO对象的总浏览次数
        repairNoticeDTO.setBrowseTimes(totalViewCount.intValue());
        // 设置公告详情的描述信息
        setDescriptions(repairNoticeDTO);
        // 创建一个消息Map，用于记录公告ID和浏览次数
        Map<String, Integer> message = new HashMap<>();
        message.put(noticeId.toString(), redisUtil.getLong(redisKey).intValue());
        // 通过RabbitMQ发送消息以记录浏览次数
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_KEY, message);
        // 返回包含公告详情和总浏览次数的DTO对象
        return repairNoticeDTO;
    }

    /**
     * 定时任务方法，每天凌晨执行
     * cron表达式"0 0 0 * * ?"表示每天凌晨0点执行
     */
    @Scheduled(cron = "0 0 0 * * ?")
    public void syncRedisToQueue() {
        // 从Redis中获取商品浏览数据
        Map<Long, Integer> productViews = getProductViewsFromRedis();
        // 将获取到的商品浏览数据发送到RabbitMQ队列中
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.ROUTING_KEY, productViews);
    }

    /**
     * 从Redis中获取所有产品的浏览量
     * @return 包含产品ID和对应浏览量的映射如果数据为空，则返回空映射
     */
    private Map<Long, Integer> getProductViewsFromRedis() {
        // 获取所有通知ID列表
        List<Long> noticeIds = getAllNoticeIds();
        // 如果通知ID列表为空，则直接返回一个空映射
        if (noticeIds.isEmpty()) {
            return Collections.emptyMap();
        }
        // 初始化一个HashMap来存储产品ID和对应的浏览量
        Map<Long, Integer> productViews = new HashMap<>();
        // 遍历通知ID列表，从Redis中获取每个产品的浏览量
        for (Long id : noticeIds) {
            // 构造Redis键值，用于存储特定产品的浏览量
            String key = RedisKey.noticeView(id.toString());
            // 从Redis中获取浏览量值
            String value = redisUtil.get(key);
            // 如果值不为空，则将其解析为整数并添加到映射中
            if (value != null) {
                productViews.put(id, Integer.parseInt(value));
            }
        }
        // 返回包含所有产品浏览量的映射
        return productViews;
    }

    /**
     * 获取所有的noticeId
     */
    private List<Long> getAllNoticeIds() {
        return repairNoticeMapper.selectAllNoticeIds();
    }

    /**
     * 验证维修通知表单的合法性
     * @param arnp 维修通知表单参数对象，包含需要验证的数据
     * @throws RepairException 如果表单无效或关键字段缺失，则抛出此异常
     */
    private void validateAddRepairNoticeForm(AddRepairNoticeParam arnp) throws RepairException {
        // 检查表单对象是否为空
        if (arnp == null) {
            throw new RepairException(EnumException.INVALID_PARAMETER);
        }
        // 检查通知标题是否为空或仅包含空白字符
        if (arnp.getNoticeTitle() == null || arnp.getNoticeTitle().trim().isEmpty()) {
            throw new RepairException(EnumException.NOTICE_TITLE_REQUIRED);
        }
        // 检查通知内容是否为空或仅包含空白字符
        if (arnp.getNoticeContent() == null || arnp.getNoticeContent().trim().isEmpty()) {
            throw new RepairException(EnumException.NOTICE_CONTENT_REQUIRED);
        }
    }

    /**
     * 验证选择维修通知表单的有效性
     * @param srnp 选择维修通知的参数对象，用于传递请求参数
     * @throws IllegalArgumentException 如果参数对象为null，则抛出此异常
     */
    private void validateSelectRepairNoticeForm(SelectRepairNoticeParam srnp) {
        // 检查传入的参数对象是否为null
        if (srnp == null) {
            // 如果参数对象为null，则抛出IllegalArgumentException异常
            throw new IllegalArgumentException("请求参数不能为空");
        }
    }

    /**
     * 验证更新维修通知表单的有效性
     * @param urnp 维修通知的更新参数对象，不能为空
     * @throws RepairException 如果参数对象为null，则抛出维修异常，表示参数无效
     */
    private void validateUpdateRepairNoticeForm(UpdateRepairNoticeParam urnp) throws RepairException {
        // 检查传入的参数对象是否为null
        if (urnp == null) {
            // 如果参数对象为null，则抛出维修异常，表明参数无效
            throw new RepairException(EnumException.INVALID_PARAMETER);
        }
    }

    /**
     * 验证公告ID的有效性
     * @param noticeId 公告ID，需要验证的值
     * @throws NoticeException 如果公告ID无效，抛出此异常
     */
    private void validateNoticeId(Long noticeId) throws NoticeException {
        // 如果公告ID为null或小于等于0，则视为无效，抛出异常
        if (noticeId == null || noticeId <= 0) {
            throw new NoticeException(EnumException.INVALID_PARAMETER);
        }
    }
    /**
     * 根据表单信息创建维修通知对象
     * @param arnp 维修通知表单参数对象，包含用户提交的表单信息
     * @return RepairNotice 初始化后的维修通知对象
     * @throws ParseException 如果解析发布时间失败，则抛出此异常
     */
    private RepairNotice createRepairNoticeFromForm(AddRepairNoticeParam arnp) throws ParseException {
        // 创建维修通知对象
        RepairNotice repairNotice = new RepairNotice();
        // 设置维修通知的标题
        repairNotice.setNoticeTitle(arnp.getNoticeTitle());
        // 设置维修通知的内容
        repairNotice.setNoticeContent(arnp.getNoticeContent());
        // 设置维修通知的状态
        repairNotice.setState(0);
        // 初始化删除标志为0，表示未删除
        repairNotice.setDelFlag(0);
        // 返回初始化后的维修通知对象
        return repairNotice;
    }

    /**
     * 根据公告ID获取维修通知
     * @param noticeId 公告ID，用于查询维修通知
     * @return RepairNotice 维修通知对象
     * @throws NoticeException 当维修通知不存在或已被删除时抛出
     */
    private RepairNotice getRepairNoticeById(Long noticeId) throws NoticeException {
        // 通过ID查询维修通知
        RepairNotice repairNotice = repairNoticeMapper.selectById(noticeId);
        // 如果维修通知不存在，抛出未找到的异常
        if (repairNotice == null) {
            throw new NoticeException(EnumException.REPAIR_NOTICE_NOT_FOUND);
        }
        // 如果维修通知已被标记为删除，抛出已删除的异常
        if (repairNotice.getDelFlag() == 1) {
            throw new NoticeException(EnumException.REPAIR_DELETED);
        }
        // 返回查询到的维修通知对象
        return repairNotice;
    }

    /**
     * 将发布的时间字符串转换为更易读的格式
     * @param publishTime 发布的时间字符串，格式为"yyyy-MM-dd HH:mm:ss"
     * @return 转换后的日期字符串，格式为"yyyy-MM-dd HH:mm:ss"
     * @throws ParseException 如果解析时间字符串时出现错误
     */
    private String formatPublishTime(String publishTime) throws ParseException {
        // 创建一个SimpleDateFormat实例，用于解析和格式化日期字符串
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 使用SimpleDateFormat解析传入的时间字符串
        Date publishDate = dateFormat.parse(publishTime);
        // 使用SimpleDateFormat格式化解析后的日期对象，并返回格式化的时间字符串
        return dateFormat.format(publishDate);
    }

    /**
     * 设置维修通知的描述信息
     * 本方法通过调用其他方法来设置维修通知中的状态和删除标志的描述信息
     * @param repairNoticeDTO 维修通知的数据传输对象
     */
    private void setDescriptions(RepairNoticeDTO repairNoticeDTO) {
        // 设置维修通知的状态描述
        setStateDescription(repairNoticeDTO);
        // 设置维修通知的删除标志描述
        setDelFlagDescription(repairNoticeDTO);
    }

    /**
     * 设置维修通知的描述信息（批量）
     * 该方法用于批量处理多个维修通知的描述信息，通过遍历每个维修通知DTO并调用setDescriptions方法来设置描述信息
     * @param repairNoticeDTOList 维修通知的DTO列表，用于批量设置描述信息
     */
    private void setDescriptions(List<RepairNoticeDTO> repairNoticeDTOList) {
        // 遍历维修通知DTO列表
        for (RepairNoticeDTO repairNoticeDTO : repairNoticeDTOList) {
            // 调用setDescriptions方法设置单个维修通知的描述信息
            setDescriptions(repairNoticeDTO);
        }
    }

    /**
     * 根据维修通知的状态码设置状态描述
     *
     * @param repairNoticeDTO 维修通知的数据传输对象
     */
    private void setStateDescription(RepairNoticeDTO repairNoticeDTO) {
        // 当状态码为1时，设置状态描述为"已发布"
        if (repairNoticeDTO.getState() == 1) {
            repairNoticeDTO.setStateDesc("已发布");
        } else {
            // 当状态码不为1时，设置状态描述为"未发布"
            repairNoticeDTO.setStateDesc("未发布");
        }
    }

    /**
     * 设置删除标志描述
     * @param repairNoticeDTO 维修通知DTO对象，包含删除标志字段
     */
    private void setDelFlagDescription(RepairNoticeDTO repairNoticeDTO) {
        // 当删除标志为0时，表示该维修通知为正常状态
        if (repairNoticeDTO.getDelFlag() == 0) {
            repairNoticeDTO.setDelFlagDesc("正常");
        } else {
            // 当删除标志非0时，表示该维修通知已被删除
            repairNoticeDTO.setDelFlagDesc("已删除");
        }
    }
}
